1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Driver for Atmel Flexcom 4 * 5 * Copyright (C) 2015 Atmel Corporation 6 * 7 * Author: Cyrille Pitchen <cyrille.pitchen@atmel.com> 8 */ 9 10 #include <linux/module.h> 11 #include <linux/types.h> 12 #include <linux/kernel.h> 13 #include <linux/platform_device.h> 14 #include <linux/of.h> 15 #include <linux/of_platform.h> 16 #include <linux/err.h> 17 #include <linux/io.h> 18 #include <linux/clk.h> 19 #include <dt-bindings/mfd/atmel-flexcom.h> 20 21 /* I/O register offsets */ 22 #define FLEX_MR 0x0 /* Mode Register */ 23 #define FLEX_VERSION 0xfc /* Version Register */ 24 25 /* Mode Register bit fields */ 26 #define FLEX_MR_OPMODE_OFFSET (0) /* Operating Mode */ 27 #define FLEX_MR_OPMODE_MASK (0x3 << FLEX_MR_OPMODE_OFFSET) 28 #define FLEX_MR_OPMODE(opmode) (((opmode) << FLEX_MR_OPMODE_OFFSET) & \ 29 FLEX_MR_OPMODE_MASK) 30 31 struct atmel_flexcom { 32 void __iomem *base; 33 u32 opmode; 34 struct clk *clk; 35 }; 36 37 static int atmel_flexcom_probe(struct platform_device *pdev) 38 { 39 struct device_node *np = pdev->dev.of_node; 40 struct resource *res; 41 struct atmel_flexcom *ddata; 42 int err; 43 44 ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL); 45 if (!ddata) 46 return -ENOMEM; 47 48 platform_set_drvdata(pdev, ddata); 49 50 err = of_property_read_u32(np, "atmel,flexcom-mode", &ddata->opmode); 51 if (err) 52 return err; 53 54 if (ddata->opmode < ATMEL_FLEXCOM_MODE_USART || 55 ddata->opmode > ATMEL_FLEXCOM_MODE_TWI) 56 return -EINVAL; 57 58 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 59 ddata->base = devm_ioremap_resource(&pdev->dev, res); 60 if (IS_ERR(ddata->base)) 61 return PTR_ERR(ddata->base); 62 63 ddata->clk = devm_clk_get(&pdev->dev, NULL); 64 if (IS_ERR(ddata->clk)) 65 return PTR_ERR(ddata->clk); 66 67 err = clk_prepare_enable(ddata->clk); 68 if (err) 69 return err; 70 71 /* 72 * Set the Operating Mode in the Mode Register: only the selected device 73 * is clocked. Hence, registers of the other serial devices remain 74 * inaccessible and are read as zero. Also the external I/O lines of the 75 * Flexcom are muxed to reach the selected device. 76 */ 77 writel(FLEX_MR_OPMODE(ddata->opmode), ddata->base + FLEX_MR); 78 79 clk_disable_unprepare(ddata->clk); 80 81 return devm_of_platform_populate(&pdev->dev); 82 } 83 84 static const struct of_device_id atmel_flexcom_of_match[] = { 85 { .compatible = "atmel,sama5d2-flexcom" }, 86 { /* sentinel */ } 87 }; 88 MODULE_DEVICE_TABLE(of, atmel_flexcom_of_match); 89 90 #ifdef CONFIG_PM_SLEEP 91 static int atmel_flexcom_resume(struct device *dev) 92 { 93 struct atmel_flexcom *ddata = dev_get_drvdata(dev); 94 int err; 95 u32 val; 96 97 err = clk_prepare_enable(ddata->clk); 98 if (err) 99 return err; 100 101 val = FLEX_MR_OPMODE(ddata->opmode), 102 writel(val, ddata->base + FLEX_MR); 103 104 clk_disable_unprepare(ddata->clk); 105 106 return 0; 107 } 108 #endif 109 110 static SIMPLE_DEV_PM_OPS(atmel_flexcom_pm_ops, NULL, 111 atmel_flexcom_resume); 112 113 static struct platform_driver atmel_flexcom_driver = { 114 .probe = atmel_flexcom_probe, 115 .driver = { 116 .name = "atmel_flexcom", 117 .pm = &atmel_flexcom_pm_ops, 118 .of_match_table = atmel_flexcom_of_match, 119 }, 120 }; 121 122 module_platform_driver(atmel_flexcom_driver); 123 124 MODULE_AUTHOR("Cyrille Pitchen <cyrille.pitchen@atmel.com>"); 125 MODULE_DESCRIPTION("Atmel Flexcom MFD driver"); 126 MODULE_LICENSE("GPL v2"); 127