Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
L
linux-elphel
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Commits
Open sidebar
Elphel
linux-elphel
Commits
f5b08ac7
Commit
f5b08ac7
authored
Jul 30, 2019
by
Oleg Dzhimiev
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
updated driver to current version, added our fall-through
parent
27a7df33
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
248 additions
and
118 deletions
+248
-118
rtc-m41t80.c
src/drivers/rtc/rtc-m41t80.c
+248
-118
No files found.
src/drivers/rtc/rtc-m41t80.c
View file @
f5b08ac7
...
...
@@ -16,10 +16,12 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/bcd.h>
#include <linux/clk-provider.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/rtc.h>
#include <linux/slab.h>
#include <linux/mutex.h>
...
...
@@ -52,6 +54,8 @@
#define M41T80_ALARM_REG_SIZE \
(M41T80_REG_ALARM_SEC + 1 - M41T80_REG_ALARM_MON)
#define M41T80_SQW_MAX_FREQ 32768
#define M41T80_SEC_ST BIT(7)
/* ST: Stop Bit */
#define M41T80_ALMON_AFE BIT(7)
/* AFE: AF Enable Bit */
#define M41T80_ALMON_SQWE BIT(6)
/* SQWE: SQW Enable Bit */
...
...
@@ -86,9 +90,71 @@ static const struct i2c_device_id m41t80_id[] = {
};
MODULE_DEVICE_TABLE
(
i2c
,
m41t80_id
);
static
const
struct
of_device_id
m41t80_of_match
[]
=
{
{
.
compatible
=
"st,m41t62"
,
.
data
=
(
void
*
)(
M41T80_FEATURE_SQ
|
M41T80_FEATURE_SQ_ALT
)
},
{
.
compatible
=
"st,m41t65"
,
.
data
=
(
void
*
)(
M41T80_FEATURE_HT
|
M41T80_FEATURE_WD
)
},
{
.
compatible
=
"st,m41t80"
,
.
data
=
(
void
*
)(
M41T80_FEATURE_SQ
)
},
{
.
compatible
=
"st,m41t81"
,
.
data
=
(
void
*
)(
M41T80_FEATURE_HT
|
M41T80_FEATURE_SQ
)
},
{
.
compatible
=
"st,m41t81s"
,
.
data
=
(
void
*
)(
M41T80_FEATURE_HT
|
M41T80_FEATURE_BL
|
M41T80_FEATURE_SQ
)
},
{
.
compatible
=
"st,m41t82"
,
.
data
=
(
void
*
)(
M41T80_FEATURE_HT
|
M41T80_FEATURE_BL
|
M41T80_FEATURE_SQ
)
},
{
.
compatible
=
"st,m41t83"
,
.
data
=
(
void
*
)(
M41T80_FEATURE_HT
|
M41T80_FEATURE_BL
|
M41T80_FEATURE_SQ
)
},
{
.
compatible
=
"st,m41t84"
,
.
data
=
(
void
*
)(
M41T80_FEATURE_HT
|
M41T80_FEATURE_BL
|
M41T80_FEATURE_SQ
)
},
{
.
compatible
=
"st,m41t85"
,
.
data
=
(
void
*
)(
M41T80_FEATURE_HT
|
M41T80_FEATURE_BL
|
M41T80_FEATURE_SQ
)
},
{
.
compatible
=
"st,m41t87"
,
.
data
=
(
void
*
)(
M41T80_FEATURE_HT
|
M41T80_FEATURE_BL
|
M41T80_FEATURE_SQ
)
},
{
.
compatible
=
"microcrystal,rv4162"
,
.
data
=
(
void
*
)(
M41T80_FEATURE_SQ
|
M41T80_FEATURE_WD
|
M41T80_FEATURE_SQ_ALT
)
},
/* DT compatibility only, do not use compatibles below: */
{
.
compatible
=
"st,rv4162"
,
.
data
=
(
void
*
)(
M41T80_FEATURE_SQ
|
M41T80_FEATURE_WD
|
M41T80_FEATURE_SQ_ALT
)
},
{
.
compatible
=
"rv4162"
,
.
data
=
(
void
*
)(
M41T80_FEATURE_SQ
|
M41T80_FEATURE_WD
|
M41T80_FEATURE_SQ_ALT
)
},
{
}
};
MODULE_DEVICE_TABLE
(
of
,
m41t80_of_match
);
struct
m41t80_data
{
u8
features
;
unsigned
long
features
;
struct
i2c_client
*
client
;
struct
rtc_device
*
rtc
;
#ifdef CONFIG_COMMON_CLK
struct
clk_hw
sqw
;
#endif
};
static
irqreturn_t
m41t80_handle_irq
(
int
irq
,
void
*
dev_id
)
...
...
@@ -142,7 +208,7 @@ static int m41t80_get_datetime(struct i2c_client *client,
return
flags
;
if
(
flags
&
M41T80_FLAGS_OF
)
{
dev_err
(
&
client
->
dev
,
"Oscillator failure, data
might be
invalid. Elphel: proceeding anyway.
\n
"
);
dev_err
(
&
client
->
dev
,
"Oscillator failure, data
is
invalid. Elphel: proceeding anyway.
\n
"
);
//return -EINVAL;
}
...
...
@@ -168,6 +234,7 @@ static int m41t80_get_datetime(struct i2c_client *client,
/* Sets the given date and time to the real time clock. */
static
int
m41t80_set_datetime
(
struct
i2c_client
*
client
,
struct
rtc_time
*
tm
)
{
struct
m41t80_data
*
clientdata
=
i2c_get_clientdata
(
client
);
unsigned
char
buf
[
8
];
int
err
,
flags
;
...
...
@@ -183,6 +250,17 @@ static int m41t80_set_datetime(struct i2c_client *client, struct rtc_time *tm)
buf
[
M41T80_REG_YEAR
]
=
bin2bcd
(
tm
->
tm_year
-
100
);
buf
[
M41T80_REG_WDAY
]
=
tm
->
tm_wday
;
/* If the square wave output is controlled in the weekday register */
if
(
clientdata
->
features
&
M41T80_FEATURE_SQ_ALT
)
{
int
val
;
val
=
i2c_smbus_read_byte_data
(
client
,
M41T80_REG_WDAY
);
if
(
val
<
0
)
return
val
;
buf
[
M41T80_REG_WDAY
]
|=
(
val
&
0xf0
);
}
err
=
i2c_smbus_write_i2c_block_data
(
client
,
M41T80_REG_SSEC
,
sizeof
(
buf
),
buf
);
if
(
err
<
0
)
{
...
...
@@ -273,6 +351,9 @@ static int m41t80_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
return
err
;
}
/* Keep SQWE bit value */
alarmvals
[
0
]
|=
(
ret
&
M41T80_ALMON_SQWE
);
ret
=
i2c_smbus_read_byte_data
(
client
,
M41T80_REG_FLAGS
);
if
(
ret
<
0
)
return
ret
;
...
...
@@ -359,116 +440,166 @@ static int m41t80_resume(struct device *dev)
static
SIMPLE_DEV_PM_OPS
(
m41t80_pm
,
m41t80_suspend
,
m41t80_resume
);
static
ssize_t
flags_show
(
struct
device
*
dev
,
struct
device_attribute
*
attr
,
char
*
buf
)
#ifdef CONFIG_COMMON_CLK
#define sqw_to_m41t80_data(_hw) container_of(_hw, struct m41t80_data, sqw)
static
unsigned
long
m41t80_sqw_recalc_rate
(
struct
clk_hw
*
hw
,
unsigned
long
parent_rate
)
{
struct
i2c_client
*
client
=
to_i2c_client
(
dev
);
int
val
;
struct
m41t80_data
*
m41t80
=
sqw_to_m41t80_data
(
hw
);
struct
i2c_client
*
client
=
m41t80
->
client
;
int
reg_sqw
=
(
m41t80
->
features
&
M41T80_FEATURE_SQ_ALT
)
?
M41T80_REG_WDAY
:
M41T80_REG_SQW
;
int
ret
=
i2c_smbus_read_byte_data
(
client
,
reg_sqw
);
unsigned
long
val
=
M41T80_SQW_MAX_FREQ
;
if
(
ret
<
0
)
return
0
;
val
=
i2c_smbus_read_byte_data
(
client
,
M41T80_REG_FLAGS
);
if
(
val
<
0
)
return
val
;
return
sprintf
(
buf
,
"%#x
\n
"
,
val
);
ret
>>=
4
;
if
(
ret
==
0
)
val
=
0
;
else
if
(
ret
>
1
)
val
=
val
/
(
1
<<
ret
);
return
val
;
}
static
DEVICE_ATTR_RO
(
flags
);
static
ssize_t
sqwfreq_show
(
struct
device
*
dev
,
struct
device_attribute
*
attr
,
char
*
buf
)
static
long
m41t80_sqw_round_rate
(
struct
clk_hw
*
hw
,
unsigned
long
rate
,
unsigned
long
*
prate
)
{
struct
i2c_client
*
client
=
to_i2c_client
(
dev
);
struct
m41t80_data
*
clientdata
=
i2c_get_clientdata
(
client
);
int
val
,
reg_sqw
;
int
i
,
freq
=
M41T80_SQW_MAX_FREQ
;
if
(
!
(
clientdata
->
features
&
M41T80_FEATURE_SQ
)
)
return
-
EINVAL
;
if
(
freq
<=
rate
)
return
freq
;
reg_sqw
=
M41T80_REG_SQW
;
if
(
clientdata
->
features
&
M41T80_FEATURE_SQ_ALT
)
reg_sqw
=
M41T80_REG_WDAY
;
val
=
i2c_smbus_read_byte_data
(
client
,
reg_sqw
);
if
(
val
<
0
)
return
val
;
val
=
(
val
>>
4
)
&
0xf
;
switch
(
val
)
{
case
0
:
break
;
case
1
:
val
=
32768
;
break
;
default:
val
=
32768
>>
val
;
for
(
i
=
2
;
i
<=
ilog2
(
M41T80_SQW_MAX_FREQ
);
i
++
)
{
freq
/=
1
<<
i
;
if
(
freq
<=
rate
)
return
freq
;
}
return
sprintf
(
buf
,
"%d
\n
"
,
val
);
return
0
;
}
static
ssize_t
sqwfreq_store
(
struct
device
*
dev
,
struct
device_attribute
*
attr
,
const
char
*
buf
,
size_t
count
)
static
int
m41t80_sqw_set_rate
(
struct
clk_hw
*
hw
,
unsigned
long
rate
,
unsigned
long
parent_rate
)
{
struct
i2c_client
*
client
=
to_i2c_client
(
dev
);
struct
m41t80_data
*
clientdata
=
i2c_get_clientdata
(
client
);
int
almon
,
sqw
,
reg_sqw
,
rc
;
unsigned
long
val
;
rc
=
kstrtoul
(
buf
,
0
,
&
val
);
if
(
rc
<
0
)
return
rc
;
if
(
!
(
clientdata
->
features
&
M41T80_FEATURE_SQ
))
return
-
EINVAL
;
if
(
val
)
{
if
(
!
is_power_of_2
(
val
))
struct
m41t80_data
*
m41t80
=
sqw_to_m41t80_data
(
hw
);
struct
i2c_client
*
client
=
m41t80
->
client
;
int
reg_sqw
=
(
m41t80
->
features
&
M41T80_FEATURE_SQ_ALT
)
?
M41T80_REG_WDAY
:
M41T80_REG_SQW
;
int
reg
,
ret
,
val
=
0
;
if
(
rate
)
{
if
(
!
is_power_of_2
(
rate
))
return
-
EINVAL
;
val
=
ilog2
(
val
);
if
(
val
==
15
)
val
=
ilog2
(
rate
);
if
(
val
==
ilog2
(
M41T80_SQW_MAX_FREQ
)
)
val
=
1
;
else
if
(
val
<
14
)
val
=
15
-
val
;
else
if
(
val
<
(
ilog2
(
M41T80_SQW_MAX_FREQ
)
-
1
)
)
val
=
ilog2
(
M41T80_SQW_MAX_FREQ
)
-
val
;
else
return
-
EINVAL
;
}
/* disable SQW, set SQW frequency & re-enable */
almon
=
i2c_smbus_read_byte_data
(
client
,
M41T80_REG_ALARM_MON
);
if
(
almon
<
0
)
return
almon
;
reg_sqw
=
M41T80_REG_SQW
;
if
(
clientdata
->
features
&
M41T80_FEATURE_SQ_ALT
)
reg_sqw
=
M41T80_REG_WDAY
;
sqw
=
i2c_smbus_read_byte_data
(
client
,
reg_sqw
);
if
(
sqw
<
0
)
return
sqw
;
sqw
=
(
sqw
&
0x0f
)
|
(
val
<<
4
);
rc
=
i2c_smbus_write_byte_data
(
client
,
M41T80_REG_ALARM_MON
,
almon
&
~
M41T80_ALMON_SQWE
);
if
(
rc
<
0
)
return
rc
;
if
(
val
)
{
rc
=
i2c_smbus_write_byte_data
(
client
,
reg_sqw
,
sqw
);
if
(
rc
<
0
)
return
rc
;
reg
=
i2c_smbus_read_byte_data
(
client
,
reg_sqw
);
if
(
reg
<
0
)
return
reg
;
rc
=
i2c_smbus_write_byte_data
(
client
,
M41T80_REG_ALARM_MON
,
almon
|
M41T80_ALMON_SQWE
);
if
(
rc
<
0
)
return
rc
;
}
return
count
;
reg
=
(
reg
&
0x0f
)
|
(
val
<<
4
);
ret
=
i2c_smbus_write_byte_data
(
client
,
reg_sqw
,
reg
);
if
(
ret
<
0
)
return
ret
;
return
-
EINVAL
;
}
static
DEVICE_ATTR_RW
(
sqwfreq
);
static
struct
attribute
*
attrs
[]
=
{
&
dev_attr_flags
.
attr
,
&
dev_attr_sqwfreq
.
attr
,
NULL
,
};
static
int
m41t80_sqw_control
(
struct
clk_hw
*
hw
,
bool
enable
)
{
struct
m41t80_data
*
m41t80
=
sqw_to_m41t80_data
(
hw
);
struct
i2c_client
*
client
=
m41t80
->
client
;
int
ret
=
i2c_smbus_read_byte_data
(
client
,
M41T80_REG_ALARM_MON
);
if
(
ret
<
0
)
return
ret
;
if
(
enable
)
ret
|=
M41T80_ALMON_SQWE
;
else
ret
&=
~
M41T80_ALMON_SQWE
;
static
struct
attribute_group
attr_group
=
{
.
attrs
=
attrs
,
return
i2c_smbus_write_byte_data
(
client
,
M41T80_REG_ALARM_MON
,
ret
);
}
static
int
m41t80_sqw_prepare
(
struct
clk_hw
*
hw
)
{
return
m41t80_sqw_control
(
hw
,
1
);
}
static
void
m41t80_sqw_unprepare
(
struct
clk_hw
*
hw
)
{
m41t80_sqw_control
(
hw
,
0
);
}
static
int
m41t80_sqw_is_prepared
(
struct
clk_hw
*
hw
)
{
struct
m41t80_data
*
m41t80
=
sqw_to_m41t80_data
(
hw
);
struct
i2c_client
*
client
=
m41t80
->
client
;
int
ret
=
i2c_smbus_read_byte_data
(
client
,
M41T80_REG_ALARM_MON
);
if
(
ret
<
0
)
return
ret
;
return
!!
(
ret
&
M41T80_ALMON_SQWE
);
}
static
const
struct
clk_ops
m41t80_sqw_ops
=
{
.
prepare
=
m41t80_sqw_prepare
,
.
unprepare
=
m41t80_sqw_unprepare
,
.
is_prepared
=
m41t80_sqw_is_prepared
,
.
recalc_rate
=
m41t80_sqw_recalc_rate
,
.
round_rate
=
m41t80_sqw_round_rate
,
.
set_rate
=
m41t80_sqw_set_rate
,
};
static
struct
clk
*
m41t80_sqw_register_clk
(
struct
m41t80_data
*
m41t80
)
{
struct
i2c_client
*
client
=
m41t80
->
client
;
struct
device_node
*
node
=
client
->
dev
.
of_node
;
struct
clk
*
clk
;
struct
clk_init_data
init
;
int
ret
;
/* First disable the clock */
ret
=
i2c_smbus_read_byte_data
(
client
,
M41T80_REG_ALARM_MON
);
if
(
ret
<
0
)
return
ERR_PTR
(
ret
);
ret
=
i2c_smbus_write_byte_data
(
client
,
M41T80_REG_ALARM_MON
,
ret
&
~
(
M41T80_ALMON_SQWE
));
if
(
ret
<
0
)
return
ERR_PTR
(
ret
);
init
.
name
=
"m41t80-sqw"
;
init
.
ops
=
&
m41t80_sqw_ops
;
init
.
flags
=
0
;
init
.
parent_names
=
NULL
;
init
.
num_parents
=
0
;
m41t80
->
sqw
.
init
=
&
init
;
/* optional override of the clockname */
of_property_read_string
(
node
,
"clock-output-names"
,
&
init
.
name
);
/* register the clock */
clk
=
clk_register
(
&
client
->
dev
,
&
m41t80
->
sqw
);
if
(
!
IS_ERR
(
clk
))
of_clk_add_provider
(
node
,
of_clk_src_simple_get
,
clk
);
return
clk
;
}
#endif
#ifdef CONFIG_RTC_DRV_M41T80_WDT
/*
*****************************************************************************
...
...
@@ -759,13 +890,6 @@ static struct notifier_block wdt_notifier = {
*****************************************************************************
*/
static
void
m41t80_remove_sysfs_group
(
void
*
_dev
)
{
struct
device
*
dev
=
_dev
;
sysfs_remove_group
(
&
dev
->
kobj
,
&
attr_group
);
}
static
int
m41t80_probe
(
struct
i2c_client
*
client
,
const
struct
i2c_device_id
*
id
)
{
...
...
@@ -774,6 +898,7 @@ static int m41t80_probe(struct i2c_client *client,
struct
rtc_device
*
rtc
=
NULL
;
struct
rtc_time
tm
;
struct
m41t80_data
*
m41t80_data
=
NULL
;
bool
wakeup_source
=
false
;
if
(
!
i2c_check_functionality
(
client
->
adapter
,
I2C_FUNC_SMBUS_I2C_BLOCK
|
I2C_FUNC_SMBUS_BYTE_DATA
))
{
...
...
@@ -786,9 +911,18 @@ static int m41t80_probe(struct i2c_client *client,
if
(
!
m41t80_data
)
return
-
ENOMEM
;
m41t80_data
->
features
=
id
->
driver_data
;
m41t80_data
->
client
=
client
;
if
(
client
->
dev
.
of_node
)
m41t80_data
->
features
=
(
unsigned
long
)
of_device_get_match_data
(
&
client
->
dev
);
else
m41t80_data
->
features
=
id
->
driver_data
;
i2c_set_clientdata
(
client
,
m41t80_data
);
#ifdef CONFIG_OF
wakeup_source
=
of_property_read_bool
(
client
->
dev
.
of_node
,
"wakeup-source"
);
#endif
if
(
client
->
irq
>
0
)
{
rc
=
devm_request_threaded_irq
(
&
client
->
dev
,
client
->
irq
,
NULL
,
m41t80_handle_irq
,
...
...
@@ -797,14 +931,16 @@ static int m41t80_probe(struct i2c_client *client,
if
(
rc
)
{
dev_warn
(
&
client
->
dev
,
"unable to request IRQ, alarms disabled
\n
"
);
client
->
irq
=
0
;
}
else
{
m41t80_rtc_ops
.
read_alarm
=
m41t80_read_alarm
;
m41t80_rtc_ops
.
set_alarm
=
m41t80_set_alarm
;
m41t80_rtc_ops
.
alarm_irq_enable
=
m41t80_alarm_irq_enable
;
/* Enable the wakealarm */
device_init_wakeup
(
&
client
->
dev
,
true
);
wakeup_source
=
false
;
}
}
if
(
client
->
irq
>
0
||
wakeup_source
)
{
m41t80_rtc_ops
.
read_alarm
=
m41t80_read_alarm
;
m41t80_rtc_ops
.
set_alarm
=
m41t80_set_alarm
;
m41t80_rtc_ops
.
alarm_irq_enable
=
m41t80_alarm_irq_enable
;
/* Enable the wakealarm */
device_init_wakeup
(
&
client
->
dev
,
true
);
}
rtc
=
devm_rtc_device_register
(
&
client
->
dev
,
client
->
name
,
&
m41t80_rtc_ops
,
THIS_MODULE
);
...
...
@@ -812,6 +948,10 @@ static int m41t80_probe(struct i2c_client *client,
return
PTR_ERR
(
rtc
);
m41t80_data
->
rtc
=
rtc
;
if
(
client
->
irq
<=
0
)
{
/* We cannot support UIE mode if we do not have an IRQ line */
rtc
->
uie_unsupported
=
1
;
}
/* Make sure HT (Halt Update) bit is cleared */
rc
=
i2c_smbus_read_byte_data
(
client
,
M41T80_REG_ALARM_HOUR
);
...
...
@@ -846,21 +986,6 @@ static int m41t80_probe(struct i2c_client *client,
return
rc
;
}
/* Export sysfs entries */
rc
=
sysfs_create_group
(
&
(
&
client
->
dev
)
->
kobj
,
&
attr_group
);
if
(
rc
)
{
dev_err
(
&
client
->
dev
,
"Failed to create sysfs group: %d
\n
"
,
rc
);
return
rc
;
}
rc
=
devm_add_action_or_reset
(
&
client
->
dev
,
m41t80_remove_sysfs_group
,
&
client
->
dev
);
if
(
rc
)
{
dev_err
(
&
client
->
dev
,
"Failed to add sysfs cleanup action: %d
\n
"
,
rc
);
return
rc
;
}
#ifdef CONFIG_RTC_DRV_M41T80_WDT
if
(
m41t80_data
->
features
&
M41T80_FEATURE_HT
)
{
save_client
=
client
;
...
...
@@ -873,6 +998,10 @@ static int m41t80_probe(struct i2c_client *client,
return
rc
;
}
}
#endif
#ifdef CONFIG_COMMON_CLK
if
(
m41t80_data
->
features
&
M41T80_FEATURE_SQ
)
m41t80_sqw_register_clk
(
m41t80_data
);
#endif
return
0
;
}
...
...
@@ -894,6 +1023,7 @@ static int m41t80_remove(struct i2c_client *client)
static
struct
i2c_driver
m41t80_driver
=
{
.
driver
=
{
.
name
=
"rtc-m41t80"
,
.
of_match_table
=
of_match_ptr
(
m41t80_of_match
),
.
pm
=
&
m41t80_pm
,
},
.
probe
=
m41t80_probe
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment