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 atmel_flexcom *ddata; 41 int err; 42 43 ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL); 44 if (!ddata) 45 return -ENOMEM; 46 47 platform_set_drvdata(pdev, ddata); 48 49 err = of_property_read_u32(np, "atmel,flexcom-mode", &ddata->opmode); 50 if (err) 51 return err; 52 53 if (ddata->opmode < ATMEL_FLEXCOM_MODE_USART || 54 ddata->opmode > ATMEL_FLEXCOM_MODE_TWI) 55 return -EINVAL; 56 57 ddata->base = devm_platform_get_and_ioremap_resource(pdev, 0, NULL); 58 if (IS_ERR(ddata->base)) 59 return PTR_ERR(ddata->base); 60 61 ddata->clk = devm_clk_get(&pdev->dev, NULL); 62 if (IS_ERR(ddata->clk)) 63 return PTR_ERR(ddata->clk); 64 65 err = clk_prepare_enable(ddata->clk); 66 if (err) 67 return err; 68 69 /* 70 * Set the Operating Mode in the Mode Register: only the selected device 71 * is clocked. Hence, registers of the other serial devices remain 72 * inaccessible and are read as zero. Also the external I/O lines of the 73 * Flexcom are muxed to reach the selected device. 74 */ 75 writel(FLEX_MR_OPMODE(ddata->opmode), ddata->base + FLEX_MR); 76 77 clk_disable_unprepare(ddata->clk); 78 79 return devm_of_platform_populate(&pdev->dev); 80 } 81 82 static const struct of_device_id atmel_flexcom_of_match[] = { 83 { .compatible = "atmel,sama5d2-flexcom" }, 84 { /* sentinel */ } 85 }; 86 MODULE_DEVICE_TABLE(of, atmel_flexcom_of_match); 87 88 static int __maybe_unused atmel_flexcom_resume_noirq(struct device *dev) 89 { 90 struct atmel_flexcom *ddata = dev_get_drvdata(dev); 91 int err; 92 u32 val; 93 94 err = clk_prepare_enable(ddata->clk); 95 if (err) 96 return err; 97 98 val = FLEX_MR_OPMODE(ddata->opmode), 99 writel(val, ddata->base + FLEX_MR); 100 101 clk_disable_unprepare(ddata->clk); 102 103 return 0; 104 } 105 106 static const struct dev_pm_ops __maybe_unused atmel_flexcom_pm_ops = { 107 .resume_noirq = atmel_flexcom_resume_noirq, 108 }; 109 110 static struct platform_driver atmel_flexcom_driver = { 111 .probe = atmel_flexcom_probe, 112 .driver = { 113 .name = "atmel_flexcom", 114 .pm = pm_ptr(&atmel_flexcom_pm_ops), 115 .of_match_table = atmel_flexcom_of_match, 116 }, 117 }; 118 119 module_platform_driver(atmel_flexcom_driver); 120 121 MODULE_AUTHOR("Cyrille Pitchen <cyrille.pitchen@atmel.com>"); 122 MODULE_DESCRIPTION("Atmel Flexcom MFD driver"); 123 MODULE_LICENSE("GPL v2"); 124