173118e61SJonghwa Lee /* 273118e61SJonghwa Lee * clk-max77686.c - Clock driver for Maxim 77686 373118e61SJonghwa Lee * 473118e61SJonghwa Lee * Copyright (C) 2012 Samsung Electornics 573118e61SJonghwa Lee * Jonghwa Lee <jonghwa3.lee@samsung.com> 673118e61SJonghwa Lee * 773118e61SJonghwa Lee * This program is free software; you can redistribute it and/or modify it 873118e61SJonghwa Lee * under the terms of the GNU General Public License as published by the 973118e61SJonghwa Lee * Free Software Foundation; either version 2 of the License, or (at your 1073118e61SJonghwa Lee * option) any later version. 1173118e61SJonghwa Lee * 1273118e61SJonghwa Lee * This program is distributed in the hope that it will be useful, 1373118e61SJonghwa Lee * but WITHOUT ANY WARRANTY; without even the implied warranty of 1473118e61SJonghwa Lee * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1573118e61SJonghwa Lee * GNU General Public License for more details. 1673118e61SJonghwa Lee * 1773118e61SJonghwa Lee * You should have received a copy of the GNU General Public License 1873118e61SJonghwa Lee * along with this program; if not, write to the Free Software 1973118e61SJonghwa Lee * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 2073118e61SJonghwa Lee * 2173118e61SJonghwa Lee */ 2273118e61SJonghwa Lee 2373118e61SJonghwa Lee #include <linux/kernel.h> 2473118e61SJonghwa Lee #include <linux/slab.h> 2573118e61SJonghwa Lee #include <linux/err.h> 2673118e61SJonghwa Lee #include <linux/platform_device.h> 2773118e61SJonghwa Lee #include <linux/mfd/max77686.h> 2873118e61SJonghwa Lee #include <linux/mfd/max77686-private.h> 2973118e61SJonghwa Lee #include <linux/clk-provider.h> 3073118e61SJonghwa Lee #include <linux/mutex.h> 3173118e61SJonghwa Lee #include <linux/clkdev.h> 3273118e61SJonghwa Lee 3373118e61SJonghwa Lee enum { 3473118e61SJonghwa Lee MAX77686_CLK_AP = 0, 3573118e61SJonghwa Lee MAX77686_CLK_CP, 3673118e61SJonghwa Lee MAX77686_CLK_PMIC, 3773118e61SJonghwa Lee MAX77686_CLKS_NUM, 3873118e61SJonghwa Lee }; 3973118e61SJonghwa Lee 4073118e61SJonghwa Lee struct max77686_clk { 4173118e61SJonghwa Lee struct max77686_dev *iodev; 4273118e61SJonghwa Lee u32 mask; 4373118e61SJonghwa Lee struct clk_hw hw; 4473118e61SJonghwa Lee struct clk_lookup *lookup; 4573118e61SJonghwa Lee }; 4673118e61SJonghwa Lee 473fe296cfSAxel Lin static struct max77686_clk *to_max77686_clk(struct clk_hw *hw) 4873118e61SJonghwa Lee { 4973118e61SJonghwa Lee return container_of(hw, struct max77686_clk, hw); 5073118e61SJonghwa Lee } 5173118e61SJonghwa Lee 5273118e61SJonghwa Lee static int max77686_clk_prepare(struct clk_hw *hw) 5373118e61SJonghwa Lee { 543fe296cfSAxel Lin struct max77686_clk *max77686 = to_max77686_clk(hw); 5573118e61SJonghwa Lee 563fe296cfSAxel Lin return regmap_update_bits(max77686->iodev->regmap, 573fe296cfSAxel Lin MAX77686_REG_32KHZ, max77686->mask, 583fe296cfSAxel Lin max77686->mask); 5973118e61SJonghwa Lee } 6073118e61SJonghwa Lee 6173118e61SJonghwa Lee static void max77686_clk_unprepare(struct clk_hw *hw) 6273118e61SJonghwa Lee { 633fe296cfSAxel Lin struct max77686_clk *max77686 = to_max77686_clk(hw); 6473118e61SJonghwa Lee 6573118e61SJonghwa Lee regmap_update_bits(max77686->iodev->regmap, 6673118e61SJonghwa Lee MAX77686_REG_32KHZ, max77686->mask, ~max77686->mask); 6773118e61SJonghwa Lee } 6873118e61SJonghwa Lee 6921c8ed2dSTomasz Figa static int max77686_clk_is_prepared(struct clk_hw *hw) 7073118e61SJonghwa Lee { 713fe296cfSAxel Lin struct max77686_clk *max77686 = to_max77686_clk(hw); 7273118e61SJonghwa Lee int ret; 7373118e61SJonghwa Lee u32 val; 7473118e61SJonghwa Lee 7573118e61SJonghwa Lee ret = regmap_read(max77686->iodev->regmap, 7673118e61SJonghwa Lee MAX77686_REG_32KHZ, &val); 7773118e61SJonghwa Lee 7873118e61SJonghwa Lee if (ret < 0) 7973118e61SJonghwa Lee return -EINVAL; 8073118e61SJonghwa Lee 8173118e61SJonghwa Lee return val & max77686->mask; 8273118e61SJonghwa Lee } 8373118e61SJonghwa Lee 84cf7d4a6fSTomasz Figa static unsigned long max77686_recalc_rate(struct clk_hw *hw, 85cf7d4a6fSTomasz Figa unsigned long parent_rate) 86cf7d4a6fSTomasz Figa { 87cf7d4a6fSTomasz Figa return 32768; 88cf7d4a6fSTomasz Figa } 89cf7d4a6fSTomasz Figa 9073118e61SJonghwa Lee static struct clk_ops max77686_clk_ops = { 9173118e61SJonghwa Lee .prepare = max77686_clk_prepare, 9273118e61SJonghwa Lee .unprepare = max77686_clk_unprepare, 9321c8ed2dSTomasz Figa .is_prepared = max77686_clk_is_prepared, 94cf7d4a6fSTomasz Figa .recalc_rate = max77686_recalc_rate, 9573118e61SJonghwa Lee }; 9673118e61SJonghwa Lee 9773118e61SJonghwa Lee static struct clk_init_data max77686_clks_init[MAX77686_CLKS_NUM] = { 9873118e61SJonghwa Lee [MAX77686_CLK_AP] = { 9973118e61SJonghwa Lee .name = "32khz_ap", 10073118e61SJonghwa Lee .ops = &max77686_clk_ops, 10173118e61SJonghwa Lee .flags = CLK_IS_ROOT, 10273118e61SJonghwa Lee }, 10373118e61SJonghwa Lee [MAX77686_CLK_CP] = { 10473118e61SJonghwa Lee .name = "32khz_cp", 10573118e61SJonghwa Lee .ops = &max77686_clk_ops, 10673118e61SJonghwa Lee .flags = CLK_IS_ROOT, 10773118e61SJonghwa Lee }, 10873118e61SJonghwa Lee [MAX77686_CLK_PMIC] = { 10973118e61SJonghwa Lee .name = "32khz_pmic", 11073118e61SJonghwa Lee .ops = &max77686_clk_ops, 11173118e61SJonghwa Lee .flags = CLK_IS_ROOT, 11273118e61SJonghwa Lee }, 11373118e61SJonghwa Lee }; 11473118e61SJonghwa Lee 115badbc542STomasz Figa static struct clk *max77686_clk_register(struct device *dev, 11673118e61SJonghwa Lee struct max77686_clk *max77686) 11773118e61SJonghwa Lee { 11873118e61SJonghwa Lee struct clk *clk; 11973118e61SJonghwa Lee struct clk_hw *hw = &max77686->hw; 12073118e61SJonghwa Lee 12173118e61SJonghwa Lee clk = clk_register(dev, hw); 12273118e61SJonghwa Lee if (IS_ERR(clk)) 123badbc542STomasz Figa return clk; 12473118e61SJonghwa Lee 125f1ba28a1SLaurent Pinchart max77686->lookup = kzalloc(sizeof(struct clk_lookup), GFP_KERNEL); 1269f58b9b9SAxel Lin if (!max77686->lookup) 127badbc542STomasz Figa return ERR_PTR(-ENOMEM); 12873118e61SJonghwa Lee 12973118e61SJonghwa Lee max77686->lookup->con_id = hw->init->name; 13073118e61SJonghwa Lee max77686->lookup->clk = clk; 13173118e61SJonghwa Lee 13273118e61SJonghwa Lee clkdev_add(max77686->lookup); 13373118e61SJonghwa Lee 134badbc542STomasz Figa return clk; 13573118e61SJonghwa Lee } 13673118e61SJonghwa Lee 137018ae93fSBill Pemberton static int max77686_clk_probe(struct platform_device *pdev) 13873118e61SJonghwa Lee { 13973118e61SJonghwa Lee struct max77686_dev *iodev = dev_get_drvdata(pdev->dev.parent); 140*3966c947STomasz Figa struct max77686_clk *max77686_clks[MAX77686_CLKS_NUM]; 141*3966c947STomasz Figa struct clk **clocks; 14273118e61SJonghwa Lee int i, ret; 14373118e61SJonghwa Lee 144*3966c947STomasz Figa clocks = devm_kzalloc(&pdev->dev, sizeof(struct clk *) 14573118e61SJonghwa Lee * MAX77686_CLKS_NUM, GFP_KERNEL); 146*3966c947STomasz Figa if (!clocks) 14773118e61SJonghwa Lee return -ENOMEM; 14873118e61SJonghwa Lee 14973118e61SJonghwa Lee for (i = 0; i < MAX77686_CLKS_NUM; i++) { 15073118e61SJonghwa Lee max77686_clks[i] = devm_kzalloc(&pdev->dev, 15173118e61SJonghwa Lee sizeof(struct max77686_clk), GFP_KERNEL); 1529f58b9b9SAxel Lin if (!max77686_clks[i]) 15373118e61SJonghwa Lee return -ENOMEM; 15473118e61SJonghwa Lee } 15573118e61SJonghwa Lee 15673118e61SJonghwa Lee for (i = 0; i < MAX77686_CLKS_NUM; i++) { 15773118e61SJonghwa Lee max77686_clks[i]->iodev = iodev; 15873118e61SJonghwa Lee max77686_clks[i]->mask = 1 << i; 15973118e61SJonghwa Lee max77686_clks[i]->hw.init = &max77686_clks_init[i]; 16073118e61SJonghwa Lee 161*3966c947STomasz Figa clocks[i] = max77686_clk_register(&pdev->dev, max77686_clks[i]); 162*3966c947STomasz Figa if (IS_ERR(clocks[i])) { 163*3966c947STomasz Figa ret = PTR_ERR(clocks[i]); 164d73ac4caSTomasz Figa dev_err(&pdev->dev, "failed to register %s\n", 165d73ac4caSTomasz Figa max77686_clks[i]->hw.init->name); 166d73ac4caSTomasz Figa goto err_clocks; 16773118e61SJonghwa Lee } 16873118e61SJonghwa Lee } 16973118e61SJonghwa Lee 170*3966c947STomasz Figa platform_set_drvdata(pdev, clocks); 17173118e61SJonghwa Lee 172b0f85177STomasz Figa return 0; 17373118e61SJonghwa Lee 174d73ac4caSTomasz Figa err_clocks: 175d73ac4caSTomasz Figa for (--i; i >= 0; --i) { 176d73ac4caSTomasz Figa clkdev_drop(max77686_clks[i]->lookup); 177d73ac4caSTomasz Figa clk_unregister(max77686_clks[i]->hw.clk); 178d73ac4caSTomasz Figa } 179d73ac4caSTomasz Figa 18073118e61SJonghwa Lee return ret; 18173118e61SJonghwa Lee } 18273118e61SJonghwa Lee 1831fc7ad5dSBill Pemberton static int max77686_clk_remove(struct platform_device *pdev) 18473118e61SJonghwa Lee { 185*3966c947STomasz Figa struct clk **clocks = platform_get_drvdata(pdev); 18673118e61SJonghwa Lee int i; 18773118e61SJonghwa Lee 18873118e61SJonghwa Lee for (i = 0; i < MAX77686_CLKS_NUM; i++) { 189*3966c947STomasz Figa struct clk_hw *hw = __clk_get_hw(clocks[i]); 190*3966c947STomasz Figa struct max77686_clk *max77686 = to_max77686_clk(hw); 191*3966c947STomasz Figa 192*3966c947STomasz Figa clkdev_drop(max77686->lookup); 193*3966c947STomasz Figa clk_unregister(clocks[i]); 19473118e61SJonghwa Lee } 19573118e61SJonghwa Lee return 0; 19673118e61SJonghwa Lee } 19773118e61SJonghwa Lee 19873118e61SJonghwa Lee static const struct platform_device_id max77686_clk_id[] = { 19973118e61SJonghwa Lee { "max77686-clk", 0}, 20073118e61SJonghwa Lee { }, 20173118e61SJonghwa Lee }; 20273118e61SJonghwa Lee MODULE_DEVICE_TABLE(platform, max77686_clk_id); 20373118e61SJonghwa Lee 20473118e61SJonghwa Lee static struct platform_driver max77686_clk_driver = { 20573118e61SJonghwa Lee .driver = { 20673118e61SJonghwa Lee .name = "max77686-clk", 20773118e61SJonghwa Lee .owner = THIS_MODULE, 20873118e61SJonghwa Lee }, 20973118e61SJonghwa Lee .probe = max77686_clk_probe, 210f9cfa630SBill Pemberton .remove = max77686_clk_remove, 21173118e61SJonghwa Lee .id_table = max77686_clk_id, 21273118e61SJonghwa Lee }; 21373118e61SJonghwa Lee 21473118e61SJonghwa Lee static int __init max77686_clk_init(void) 21573118e61SJonghwa Lee { 21673118e61SJonghwa Lee return platform_driver_register(&max77686_clk_driver); 21773118e61SJonghwa Lee } 21873118e61SJonghwa Lee subsys_initcall(max77686_clk_init); 21973118e61SJonghwa Lee 22073118e61SJonghwa Lee static void __init max77686_clk_cleanup(void) 22173118e61SJonghwa Lee { 22273118e61SJonghwa Lee platform_driver_unregister(&max77686_clk_driver); 22373118e61SJonghwa Lee } 22473118e61SJonghwa Lee module_exit(max77686_clk_cleanup); 22573118e61SJonghwa Lee 22673118e61SJonghwa Lee MODULE_DESCRIPTION("MAXIM 77686 Clock Driver"); 22773118e61SJonghwa Lee MODULE_AUTHOR("Jonghwa Lee <jonghwa3.lee@samsung.com>"); 22873118e61SJonghwa Lee MODULE_LICENSE("GPL"); 229